package com.author.menubar;

import com.author.surface.Surface;
import com.author.surface.TextPane;
import com.author.surface.TextPanel;

import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.nio.channels.FileLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;

public class FileMenu extends JMenu {

	/** New */
	private JMenuItem		newMenuItem		= new JMenuItem("New");

	/** Open */
	private JMenuItem		openMenuItem	= new JMenuItem("Open");

	/** Close */
	private JMenuItem		closeMenuItem	= new JMenuItem("Close");

	/** Save */
	private JMenuItem		saveMenuItem	= new JMenuItem("Save");

	/** Save As */
	private JMenuItem		saveAsMenuItem	= new JMenuItem("Save As");

	/** Print */
	private JMenuItem		printMenuItem	= new JMenuItem("Print");

	/** Exit */
	private JMenuItem		exitMenuItem	= new JMenuItem("Exit");

	private static String	fileName		= null;

	public FileMenu(String menuname) {
		super(menuname);

		/* Add menu items for File Menu */
		this.add(newMenuItem);

		this.add(openMenuItem);
		openMenuItem.setMnemonic('O');
		openMenuItem.setAccelerator(KeyStroke.getKeyStroke(
				java.awt.event.KeyEvent.VK_O, java.awt.Event.CTRL_MASK));

		this.add(closeMenuItem);

		/* Add action listener for Open */
		this.openMenuItem.addActionListener(new OpenMenuAction());

		/* Add item separator */
		this.addSeparator();

		/* Add "Save" and "Save As" */
		this.add(saveMenuItem);
		saveMenuItem.setMnemonic('S');
		saveMenuItem.setAccelerator(KeyStroke.getKeyStroke(
				java.awt.event.KeyEvent.VK_S, java.awt.Event.CTRL_MASK));

		this.add(saveAsMenuItem);
		saveAsMenuItem.setAccelerator(KeyStroke.getKeyStroke(
				java.awt.event.KeyEvent.VK_S, java.awt.Event.CTRL_MASK
						+ java.awt.Event.ALT_MASK));

		/* Add action listener for Save */
		final String saveDialogTitle = "Save";
		this.saveMenuItem
				.addActionListener(new SaveMenuAction(saveDialogTitle));

		/* Add action listener for Save As */
		final String saveAsDialogTitle = "Save As";
		this.saveAsMenuItem.addActionListener(new SaveMenuAction(
				saveAsDialogTitle));

		/* Add item separator */
		this.addSeparator();

		/* Add "Print" */
		this.add(printMenuItem);
		printMenuItem.setMnemonic('P');
		printMenuItem.setAccelerator(KeyStroke.getKeyStroke(
				java.awt.event.KeyEvent.VK_P, java.awt.Event.CTRL_MASK));

		/* Add item separator */
		this.addSeparator();

		/* Add Exit */
		this.add(exitMenuItem);
		exitMenuItem.addActionListener(new ExitMenuAction());
	}

	static class SaveMenuAction implements ActionListener {
		SaveMenuAction(String dialogTitle) {
			this.dialogTitle = dialogTitle;
		}

		/**
		 * Name of the warning dialog which is shown to the user in case the
		 * application is not able to write the contents of the text pane to the
		 * underlying file. This will happen when the application is not able to
		 * obtain a file lock on the underlying file
		 */

		public static final String	WARNING_DIALOG_TITLE	= "Warning";

		/* File name */
		// private String fileName = null;

		/* Warning dialog message */
		public String				WARNING_DIALOG_MESSAGE	= "Unable to write to file "
																	+ fileName
																	+ " as it is currently unavailable!";

		@Override
		public void actionPerformed(ActionEvent ae) {
			if (fileName != null) {
				writeFile();
			}

			/*
			 * In case fileName is null, then interpret it as first time write
			 * to the file. So prompt the user to enter a file name
			 */
			String newFileName = null;
			newFileName = showFileGUI();

			/* Check new file name */
			if (newFileName == null) {
				return;
			}

			/* Write the contents of the text pane to file */
			fileName = newFileName;
			writeFile();
		}

		private void writeFile() {
			/*
			 * Write the contents of the text pane to the underlying physical
			 * file. Before writing the contents acquire a lock on the file
			 */
			Surface surface = (Surface) Surface.getAppSurface();
			EditorKit kit = TextPane.getTextPaneEditorKit();
			HTMLDocument doc = (HTMLDocument) TextPane.getTextPaneDocument();

			if (!(kit instanceof HTMLEditorKit)) {
				return;
			}

			FileOutputStream fileOut = null;
			FileLock fileLock = null;
			try {
				fileOut = new FileOutputStream(new File(fileName));
				fileLock = new RandomAccessFile(fileName, "rw").getChannel()
						.lock();

				if (fileLock.isValid()) {
					kit.write(fileOut, doc, 0, doc.getLength() - 1);
				} else {

					/*
					 * Inform user that update can't be performed as the file is
					 * currently locked by another process
					 */
					JOptionPane.showMessageDialog(surface,
							WARNING_DIALOG_MESSAGE, WARNING_DIALOG_TITLE,
							JOptionPane.WARNING_MESSAGE);
				}
			} catch (FileNotFoundException fnfe) {
				fnfe.printStackTrace();
			} catch (IOException ioe) {
				ioe.printStackTrace();
			} catch (BadLocationException ble) {
				ble.printStackTrace();
			} finally {
				/* Close stream */
				try {
					fileOut.close();
				} catch (IOException ioe) {
					ioe.printStackTrace();
				}

				/* Release lock */
				if (fileLock != null) {
					try {
						fileLock.release();
					} catch (IOException ioe) {
						ioe.printStackTrace();
					}
				}
			}
		}

		private String showFileGUI() {
			/* Choose new file */
			JFileChooser fc = new JFileChooser();
			if (this.dialogTitle != null) {
				fc.setDialogTitle(dialogTitle);
			}
			int returnVal = fc.showSaveDialog(Surface.getAppSurface());

			if (returnVal == JFileChooser.CANCEL_OPTION) {
				return null;
			}

			File newfile = null;
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				newfile = fc.getSelectedFile();
				if (newfile == null) {

					/* Indicate invalid file extension */
					JOptionPane.showMessageDialog(Surface.getAppSurface(),
							ExitMenuAction.INVALID_FILE_TYPE_MESSAGE);
					return null;
				}
			}

			String filename = newfile.getName();
			if ((filename.endsWith(".html")) || (filename.endsWith(".htm"))) {
				try {
					filename = newfile.getCanonicalPath();
				} catch (Exception e) {
					return null;
				}
			} else {

				/* Indicate invalid file extension */
				JOptionPane.showMessageDialog(Surface.getAppSurface(),
						ExitMenuAction.INVALID_FILE_TYPE_MESSAGE);
				return null;
			}

			return filename;
		}

		String						dialogTitle			= null;		// Name
																		// of
																		// the
																		// File
																		// dialog

		/** Name of the File dialog */
		public static final String	FILE_DIALOG_SAVE	= "Save";
		public static final String	FILE_DIALOG_SAVE_AS	= "Save As";
	}

	static class ExitMenuAction implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent ae) {

			/*
			 * First check if the document is empty or if there is something
			 * written to it
			 */
			Document doc = TextPane.getTextPaneDocument();
			if (doc.getLength() == 0) {
				System.exit(0); // Nothing updated, simply exit
			}

			/* Warn user to save content */
			Object[] options = { "No", "Yes", "Cancel" };
			int choice = JOptionPane.showOptionDialog(Surface.getAppSurface(),
					EXIT_WARNING_DIALOG_MESSAGE, EXIT_WARNING_DIALOG_TITLE,
					JOptionPane.YES_NO_CANCEL_OPTION,
					JOptionPane.WARNING_MESSAGE, null, options, options[0]);

			/* Further actions are dependent on the user choice */
			if (choice == JOptionPane.CANCEL_OPTION) {
				return; // Do nothing - the user has canceled his action
			}

			if (choice == JOptionPane.OK_OPTION) {
				/*
				 * In case fileName is null, then interpret it as first time
				 * write to the file. So prompt the user to enter a file name
				 */
				String newFileName = null;
				newFileName = showFileGUI();

				/* Check new file name */
				if (newFileName == null) {
					return;
				}

				/* Write the contents of the text pane to file */
				fileName = newFileName;
				writeFile();
			}

			if (choice == JOptionPane.NO_OPTION) {
				System.exit(0); // Exit application
			}
		}

		private void writeFile() {
			/*
			 * Write the contents of the text pane to the underlying physical
			 * file. Before writing the contents acquire a lock on the file
			 */
			Surface surface = (Surface) Surface.getAppSurface();
			EditorKit kit = TextPane.getTextPaneEditorKit();
			HTMLDocument doc = (HTMLDocument) TextPane.getTextPaneDocument();

			if (!(kit instanceof HTMLEditorKit)) {
				return;
			}

			FileOutputStream fileOut = null;
			FileLock fileLock = null;
			try {
				fileOut = new FileOutputStream(new File(fileName));
				fileLock = new RandomAccessFile(fileName, "rw").getChannel()
						.lock();

				if (fileLock.isValid()) {
					kit.write(fileOut, doc, 0, doc.getLength() - 1);
				} else {

					/*
					 * Inform user that update can't be performed as the file is
					 * currently locked by another process
					 */
					JOptionPane.showMessageDialog(surface,
							WARNING_DIALOG_MESSAGE,
							SaveMenuAction.WARNING_DIALOG_TITLE,
							JOptionPane.WARNING_MESSAGE);
				}
			} catch (FileNotFoundException fnfe) {
				fnfe.printStackTrace();
			} catch (IOException ioe) {
				ioe.printStackTrace();
			} catch (BadLocationException ble) {
				ble.printStackTrace();
			} finally {
				/* Close stream */
				try {
					fileOut.close();
				} catch (IOException ioe) {
					ioe.printStackTrace();
				}

				/* Release lock */
				if (fileLock != null) {
					try {
						fileLock.release();
					} catch (IOException ioe) {
						ioe.printStackTrace();
					}
				}
			}
		}

		private String showFileGUI() {
			/* Choose new file */
			JFileChooser fc = new JFileChooser();
			int returnVal = fc.showSaveDialog(Surface.getAppSurface());

			if (returnVal == JFileChooser.CANCEL_OPTION) {
				return null;
			}

			File newfile = null;
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				newfile = fc.getSelectedFile();
				if (newfile == null) {

					/* Indicate invalid file extension */
					JOptionPane.showMessageDialog(Surface.getAppSurface(),
							INVALID_FILE_TYPE_MESSAGE);
					return null;
				}
			}

			String filename = newfile.getName();
			if ((filename.endsWith(".html")) || (filename.endsWith(".htm"))) {
				try {
					filename = newfile.getCanonicalPath();
				} catch (Exception e) {
					return null;
				}
			} else {

				/* Indicate invalid file extension */
				JOptionPane.showMessageDialog(Surface.getAppSurface(),
						INVALID_FILE_TYPE_MESSAGE);
				return null;
			}

			return filename;
		}

		/** Exit dialog message and title */
		public static final String	EXIT_WARNING_DIALOG_MESSAGE	= "Do you want to exit without saving?";
		public static final String	EXIT_WARNING_DIALOG_TITLE	= "Exit without save?";

		/** Invalid file type message */
		public static final String	INVALID_FILE_TYPE_MESSAGE	= "File extension is invalid. Valid extensions are .htm/.html";

		// private String fileName = null; //Name of the file

		/*
		 * Warning message to indicate to the user that the file is currently
		 * unavailable for writing
		 */
		public String				WARNING_DIALOG_MESSAGE		= "Unable to write to file "
																		+ fileName
																		+ " as it is currently unavailable!";

	}

	static class OpenMenuAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent ae) {
			fileName = showFileGUI();

			if (fileName != null) {
				readFile(); /* Read file from disk */
			}
		}

		private void readFile() {
			HTMLEditorKit kit = (HTMLEditorKit) TextPane.getTextPaneEditorKit();
			HTMLDocument doc = (HTMLDocument) TextPane.getTextPaneDocument();

			try {
				/* Read the contents of the file into the text pane */
				FileReader fr = new FileReader(fileName);
				kit.read(fr, doc, 1);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (BadLocationException e) {
				e.printStackTrace();
			}
		}

		private String showFileGUI() {
			/* Choose file to open */
			JFileChooser fc = new JFileChooser();
			int returnVal = fc.showOpenDialog(Surface.getAppSurface());

			if (returnVal == JFileChooser.CANCEL_OPTION) {
				return null;
			}

			File newfile = null;
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				newfile = fc.getSelectedFile();
				if (newfile == null) {

					/* Indicate unknown file extension */
					JOptionPane.showMessageDialog(Surface.getAppSurface(),
							UNKWN_FILE_TYPE_MESSAGE);
					return null;
				}
			}

			String filename = newfile.getName();
			if ((filename.endsWith(".html")) || (filename.endsWith(".htm"))) {
				try {
					filename = newfile.getCanonicalPath();
				} catch (Exception e) {
					return null;
				}
			} else {

				/* Indicate unknown file extension */
				JOptionPane.showMessageDialog(Surface.getAppSurface(),
						UNKWN_FILE_TYPE_MESSAGE);
				return null;
			}

			return filename;
		}

		// String fileName = null; //Name of the file to be opened

		/** Invalid file type message */
		public String	UNKWN_FILE_TYPE_MESSAGE	= "Unknown file  type. Currently only html file support is available";
	}
}
